package com.sh.business.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.sh.business.config.RabbitmqSender;
import com.sh.business.domain.User;
import com.sh.business.domain.dto.DelayMessageDto;
import com.sh.business.domain.dto.UserWithdrawalDto;
import com.sh.business.domain.vo.UserWithdrawalDataVo;
import com.sh.business.mapper.UserMapper;
import com.sh.business.service.facade.UserFoundServiceFacade;
import com.sh.common.core.enums.*;
import com.sh.common.core.exception.ServiceException;
import com.sh.common.core.utils.*;
import com.sh.common.mybatis.core.page.TableDataInfo;
import com.sh.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sh.common.mybatis.helper.DataBaseHelper;
import com.sh.common.satoken.utils.LoginHelper;
import com.sh.system.service.ISysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.sh.business.domain.bo.UserWithdrawalBo;
import com.sh.business.domain.vo.UserWithdrawalVo;
import com.sh.business.domain.UserWithdrawal;
import com.sh.business.mapper.UserWithdrawalMapper;
import com.sh.business.service.IUserWithdrawalService;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;

/**
 * 用户提现记录Service业务层处理
 *
 * @author ruoyi
 * @date 2024-10-08
 */
@RequiredArgsConstructor
@Service
public class UserWithdrawalServiceImpl implements IUserWithdrawalService {

    private final UserWithdrawalMapper baseMapper;
    private final UserMapper userMapper;
    private final UserFoundServiceFacade userFoundServiceFacade;
    private final RabbitmqSender rabbitmqSender;
    private final ISysConfigService iSysConfigService;

    @Override
    public UserWithdrawalDataVo queryWithdrawalData(UserWithdrawalBo bo) {
        List<UserWithdrawalDto> userWithdrawalDtos = baseMapper.queryWithdrawalData(bo);
        UserWithdrawalDataVo userWithdrawalDataVo = new UserWithdrawalDataVo();
        Map<Integer, UserWithdrawalDto> map = StreamUtils.toMap(userWithdrawalDtos, UserWithdrawalDto::getType, Function.identity());
        if (map.keySet().contains(UserFoundEnum.USDT.getCode())) {
            UserWithdrawalDto userWithdrawalDto = map.get(UserFoundEnum.USDT.getCode());
            userWithdrawalDataVo.setUsdtAmount(userWithdrawalDto.getAmount());
            userWithdrawalDataVo.setUsdtFeeAmount(userWithdrawalDto.getFeeAmount());
            userWithdrawalDataVo.setUsdtActualAmount(userWithdrawalDto.getActualAmount());
        }
        if (map.keySet().contains(UserFoundEnum.ALEO.getCode())) {
            UserWithdrawalDto userWithdrawalDto = map.get(UserFoundEnum.ALEO.getCode());
            userWithdrawalDataVo.setAleoAmount(userWithdrawalDto.getAmount());
            userWithdrawalDataVo.setAleoFeeAmount(userWithdrawalDto.getFeeAmount());
            userWithdrawalDataVo.setAleoActualAmount(userWithdrawalDto.getActualAmount());
        }
        if (map.keySet().contains(UserFoundEnum.SCR.getCode())) {
            UserWithdrawalDto userWithdrawalDto = map.get(UserFoundEnum.SCR.getCode());
            userWithdrawalDataVo.setScrAmount(userWithdrawalDto.getAmount());
            userWithdrawalDataVo.setScrFeeAmount(userWithdrawalDto.getFeeAmount());
            userWithdrawalDataVo.setScrActualAmount(userWithdrawalDto.getActualAmount());
        }
        return userWithdrawalDataVo;
    }

    /**
     * 查询用户提现记录
     *
     * @param id 主键
     * @return 用户提现记录
     */
    @Override
    public UserWithdrawalVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询用户提现记录列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 用户提现记录分页列表
     */
    @Override
    public TableDataInfo<UserWithdrawalVo> queryPageList(UserWithdrawalBo bo, PageQuery pageQuery) {
        if (StringUtils.isNotBlank(bo.getMark())) {
            List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(User::getUserId).eq(User::getMark, bo.getMark()));
            if (CollUtil.isEmpty(userList)) {
                return TableDataInfo.build(new Page<>());
            }
            bo.setUserIds(StreamUtils.toList(userList, User::getUserId));
        }
        LambdaQueryWrapper<UserWithdrawal> lqw = buildQueryWrapper(bo);
        lqw.orderByDesc(UserWithdrawal::getCreateTime);
        Page<UserWithdrawalVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        List<UserWithdrawalVo> records = result.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(User::getUserId, User::getIsNew, User::getWalletAddress, User::getAleoAddress, User::getMark).in(User::getUserId, StreamUtils.toSet(records, UserWithdrawalVo::getUserId)));
            Map<Long, User> userMap = StreamUtils.toMap(userList, User::getUserId, Function.identity());
            records.forEach(userWithdrawalVo -> {
                User user = userMap.get(userWithdrawalVo.getUserId());
                // 提现地址 usdt 和 scr 同一个地址
                /*if (userWithdrawalVo.getType().equals(WithdrawTypeEnum.USDT.getCode()) || userWithdrawalVo.getType().equals(WithdrawTypeEnum.SCR.getCode())) {
                    userWithdrawalVo.setFromAddress(user.getWalletAddress());
                } else {
                    userWithdrawalVo.setFromAddress(user.getAleoAddress());
                }*/
                userWithdrawalVo.setMark(user.getMark());
                userWithdrawalVo.setIsNew(user.getIsNew());
            });
        }
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的用户提现记录列表
     *
     * @param bo 查询条件
     * @return 用户提现记录列表
     */
    @Override
    public List<UserWithdrawalVo> queryList(UserWithdrawalBo bo) {
        LambdaQueryWrapper<UserWithdrawal> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<UserWithdrawal> buildQueryWrapper(UserWithdrawalBo bo) {

        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<UserWithdrawal> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getUserId() != null, UserWithdrawal::getUserId, bo.getUserId());
        lqw.eq(bo.getType() != null, UserWithdrawal::getType, bo.getType());
        lqw.eq(StringUtils.isNotBlank(bo.getOrderSn()), UserWithdrawal::getOrderSn, bo.getOrderSn());
        lqw.eq(bo.getAmount() != null, UserWithdrawal::getAmount, bo.getAmount());
        lqw.eq(bo.getFeeAmount() != null, UserWithdrawal::getFeeAmount, bo.getFeeAmount());
        lqw.eq(bo.getActualAmount() != null, UserWithdrawal::getActualAmount, bo.getActualAmount());
        lqw.eq(StringUtils.isNotBlank(bo.getTxHash()), UserWithdrawal::getTxHash, bo.getTxHash());
        lqw.eq(bo.getStatus() != null, UserWithdrawal::getStatus, bo.getStatus());
        lqw.eq(bo.getTransferTime() != null, UserWithdrawal::getTransferTime, bo.getTransferTime());
        lqw.eq(StringUtils.isNotBlank(bo.getFromAddress()), UserWithdrawal::getFromAddress, bo.getFromAddress());
        lqw.eq(StringUtils.isNotBlank(bo.getNotifyData()), UserWithdrawal::getNotifyData, bo.getNotifyData());
        lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, UserWithdrawal::getCreateTime, bo.getStartTime(), bo.getEndTime());
        lqw.in(CollUtil.isNotEmpty(bo.getUserIds()), UserWithdrawal::getUserId, bo.getUserIds());
        return lqw;
    }

    /**
     * 新增用户提现记录
     *
     * @param bo 用户提现记录
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(UserWithdrawalBo bo) {
        if (bo.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("Withdrawal amount incorrect");
        }
        User user = userMapper.selectById(LoginHelper.getUserId());
        if (!BCrypt.checkpw(bo.getPassword(), user.getPassword())) {
            throw new ServiceException("password incorrect");
        }

        if (Objects.equals(bo.getType(), WithdrawTypeEnum.USDT.getCode())) {
            Boolean enable = user.getHasUsdtWithdraw();
            if (!enable) {
                throw new ServiceException("Currently Unavailable");
            }
            if (user.getUsdt().compareTo(bo.getAmount()) < 0) {
                throw new ServiceException("Insufficient.balance");
            }
            BigDecimal minAmount = Convert.toBigDecimal(iSysConfigService.selectConfigByKey("biz.min.usdt.amount"));
            if (bo.getAmount().compareTo(minAmount) < 0) {
                throw new ServiceException("Minimum withdrawal amount " + minAmount);
            }
            bo.setFromAddress(user.getWalletAddress());
        } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.ALEO.getCode())) {
            Boolean enable = user.getHasAleoWithdraw();
            if (!enable) {
                throw new ServiceException("Currently Unavailable");
            }
            if (user.getAleo().compareTo(bo.getAmount()) < 0) {
                throw new ServiceException("Insufficient.balance");
            }
            BigDecimal minAmount = Convert.toBigDecimal(iSysConfigService.selectConfigByKey("biz.min.aleo.amount"));
            if (bo.getAmount().compareTo(minAmount) < 0) {
                throw new ServiceException("Minimum withdrawal amount " + minAmount);
            }
            bo.setFromAddress(user.getAleoAddress());
        } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.SCR.getCode())) {
            Boolean enable = user.getHasScrWithdraw();
            if (!enable) {
                throw new ServiceException("Currently Unavailable");
            }
            if (user.getScr().compareTo(bo.getAmount()) < 0) {
                throw new ServiceException("Insufficient.balance");
            }
            BigDecimal minAmount = Convert.toBigDecimal(iSysConfigService.selectConfigByKey("biz.min.scr.amount"));
            if (bo.getAmount().compareTo(minAmount) < 0) {
                throw new ServiceException("Minimum withdrawal amount " + minAmount);
            }
            bo.setFromAddress(user.getWalletAddress());
        }

        bo.setUserId(user.getUserId());
        bo.setOrderSn(OrderSnUtil.generateOrderSn(OrderTypeEnum.TX));
        bo.setStatus(WithdrawStatusEnum.WAIT_AUDIT.getCode());
        UserWithdrawal add = MapstructUtils.convert(bo, UserWithdrawal.class);
        BigDecimal feeAmount = NumberUtil.mul(bo.getAmount(), Convert.toBigDecimal(iSysConfigService.selectConfigByKey("biz.withdrawal.fee.ratio")));
        add.setFeeAmount(feeAmount);
        add.setActualAmount(NumberUtil.sub(add.getAmount(), feeAmount));

        // scr提现按单笔收费
        if (Objects.equals(bo.getType(), WithdrawTypeEnum.SCR.getCode())) {
            feeAmount = Convert.toBigDecimal(iSysConfigService.selectConfigByKey("biz.scr.withdraw.fee.ratio"));
            add.setFeeAmount(feeAmount);
            add.setActualAmount(NumberUtil.sub(add.getAmount(), feeAmount));
        }

        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
            if (Objects.equals(bo.getType(), WithdrawTypeEnum.USDT.getCode())) {
                // 用户扣除usdt
                userFoundServiceFacade.modifyUserFound(user.getUserId(),
                    bo.getOrderSn(),
                    bo.getTxHash(),
                    UserFoundEnum.USDT,
                    TransferFlagEnum.TRANSFER_OUT,
                    bo.getAmount(),
                    TransferDescEnum.WITHDRAW.getCode().toString(),
                    null);
                String usdtFeeAddress = iSysConfigService.selectConfigByKey("biz.usdt.fee.address");
                User usdtFeeUser = userMapper.queryByWalletAddress(usdtFeeAddress);
                if (ObjectUtil.isNotNull(usdtFeeUser)) {
                    userFoundServiceFacade.modifyUserFound(usdtFeeUser.getUserId(),
                        bo.getOrderSn(),
                        bo.getTxHash(),
                        UserFoundEnum.USDT,
                        TransferFlagEnum.TRANSFER_IN,
                        feeAmount,
                        TransferDescEnum.WITHDRAW_FEE.getCode().toString(),
                        null);
                }

            } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.ALEO.getCode())) {
                // 用户扣除usdt
                userFoundServiceFacade.modifyUserFound(user.getUserId(),
                    bo.getOrderSn(),
                    bo.getTxHash(),
                    UserFoundEnum.ALEO,
                    TransferFlagEnum.TRANSFER_OUT,
                    bo.getAmount(),
                    TransferDescEnum.WITHDRAW.getCode().toString(),
                    null);

                String aleoFeeAddress = iSysConfigService.selectConfigByKey("biz.aleo.fee.address");
                User aleoFeeUser = userMapper.queryByWalletAddress(aleoFeeAddress);
                if (ObjectUtil.isNotNull(aleoFeeUser)) {
                    userFoundServiceFacade.modifyUserFound(aleoFeeUser.getUserId(),
                        bo.getOrderSn(),
                        bo.getTxHash(),
                        UserFoundEnum.ALEO,
                        TransferFlagEnum.TRANSFER_IN,
                        feeAmount,
                        TransferDescEnum.WITHDRAW_FEE.getCode().toString(),
                        null);
                }

            } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.SCR.getCode())) {
                // 用户扣除scr
                userFoundServiceFacade.modifyUserFound(user.getUserId(),
                    bo.getOrderSn(),
                    bo.getTxHash(),
                    UserFoundEnum.SCR,
                    TransferFlagEnum.TRANSFER_OUT,
                    bo.getAmount(),
                    TransferDescEnum.WITHDRAW.getCode().toString(),
                    null);

                String scrFeeAddress = iSysConfigService.selectConfigByKey("biz.scr.fee.address");
                User scrFeeUser = userMapper.queryByWalletAddress(scrFeeAddress);
                if (ObjectUtil.isNotNull(scrFeeUser)) {
                    userFoundServiceFacade.modifyUserFound(scrFeeUser.getUserId(),
                        bo.getOrderSn(),
                        bo.getTxHash(),
                        UserFoundEnum.SCR,
                        TransferFlagEnum.TRANSFER_IN,
                        feeAmount,
                        TransferDescEnum.WITHDRAW_FEE.getCode().toString(),
                        null);
                }
            }

        }
        return flag;
    }

    /**
     * 修改用户提现记录
     *
     * @param bo 用户提现记录
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(UserWithdrawalBo bo) {
        UserWithdrawal userWithdrawal = baseMapper.selectById(bo.getId());
        UserWithdrawal update = new UserWithdrawal();
        update.setId(bo.getId());
        if (StringUtils.isNotBlank(bo.getTxHash()) && Objects.equals(userWithdrawal.getType(), WithdrawTypeEnum.USDT.getCode())) {
            update.setTxHash(bo.getTxHash());
            update.setStatus(WithdrawStatusEnum.WAIT_CONFIRM.getCode());

            // 发送查询mq
            DelayMessageDto delayMessageDto = new DelayMessageDto();
            delayMessageDto.setType(MessageTypeEnum.WITHDRAW.getCode());
            delayMessageDto.setTxHash(bo.getTxHash());
            delayMessageDto.setCreateTime(DateUtils.getNowDate());
            rabbitmqSender.sendTtlMsg(delayMessageDto, null);
        } else {
            update.setTxHash(bo.getTxHash());
            update.setStatus(bo.getStatus());
        }
        int result = baseMapper.updateById(update);
        if (result > 0) {
            // 审核拒绝
            if (WithdrawStatusEnum.CONFIRM_ERROR.getCode().equals(bo.getStatus()) || WithdrawStatusEnum.AUDIT_REJECT.getCode().equals(bo.getStatus())) {
                if (Objects.equals(bo.getType(), WithdrawTypeEnum.USDT.getCode())) {

                    // 用户usdt增加
                    userFoundServiceFacade.modifyUserFound(userWithdrawal.getUserId(),
                        userWithdrawal.getOrderSn(),
                        null,
                        UserFoundEnum.USDT,
                        TransferFlagEnum.TRANSFER_IN,
                        userWithdrawal.getAmount(),
                        TransferDescEnum.WITHDRAW_AUDIT_REJECT.getCode().toString(),
                        null);
                } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.ALEO.getCode())) {
                    // 用户usdt增加
                    userFoundServiceFacade.modifyUserFound(userWithdrawal.getUserId(),
                        userWithdrawal.getOrderSn(),
                        null,
                        UserFoundEnum.ALEO,
                        TransferFlagEnum.TRANSFER_IN,
                        userWithdrawal.getAmount(),
                        TransferDescEnum.WITHDRAW_AUDIT_REJECT.getCode().toString(),
                        null);
                } else if (Objects.equals(bo.getType(), WithdrawTypeEnum.SCR.getCode())) {
                    // 用户scr增加
                    userFoundServiceFacade.modifyUserFound(userWithdrawal.getUserId(),
                        userWithdrawal.getOrderSn(),
                        null,
                        UserFoundEnum.SCR,
                        TransferFlagEnum.TRANSFER_IN,
                        userWithdrawal.getAmount(),
                        TransferDescEnum.WITHDRAW_AUDIT_REJECT.getCode().toString(),
                        null);
                }
            } else if (WithdrawStatusEnum.CONFIRM_RECEIPT.getCode().equals(bo.getStatus())) {
                // 确认已到账，直接修改状态
            }
        }
        return result > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(UserWithdrawal entity){
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除用户提现记录信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
}
